package org.heydr.common.http;

import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;

import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpVersion;
import org.apache.http.client.params.CookiePolicy;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.HttpEntityWrapper;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.RequestUserAgent;

public class HttpClientFactory {

	private static AbstractHttpClient httpClient;

	private static HttpParams params;

	private static ClientConnectionManager connectionManager;

	private static IdleConnectionEvictor idleConnectionEvictor;

	private static int timeout = 60000; // 从连接池获取连接的超时时间(单位:毫秒)
	private static int soTimeout = 60000; // socket读超时时间(单位:毫秒)
	private static int socketBufferSize = 8192; // socket缓存大小(单位:字节)
	private static int connectionTimeout = 60000; // socket连接超时时间(单位:毫秒)
	private static int maxConnections = 10000; // 连接池最大连接数
	private static int maxConnectionsPerRoute = 50; // 每个主机的最大连接数
	private static int connectionIdleTime = 120000; // 连接最大空闲时间(单位:毫秒)，超过此值,则销毁这条连接
	private static long keepAliveDuration = 60000L; // 连接保活时间(单位:毫秒)

	public static AbstractHttpClient getHttpClient() {
		if (httpClient == null) {

			if (params == null) {
				params = createHttpParams();// 创建http参数
			}

			if (connectionManager == null) {
				connectionManager = createThreadSafeClientConnManager(params);// 创建连接管理器
				idleConnectionEvictor = startIdleConnectionEvictor(
						connectionManager, connectionIdleTime); // 启动一个线程监听连接管理器中的连接，当其中的空闲连接空闲了指定的时间，则把它关闭!
			}

			httpClient = createHttpClient(params, connectionManager); // 创建httpclient
		}
		return httpClient;
	}

	public static AbstractHttpClient getNewHttpClient() {
		if (connectionManager == null) {
			connectionManager = createThreadSafeClientConnManager(params);
			idleConnectionEvictor = startIdleConnectionEvictor(
					connectionManager, connectionIdleTime);
		}

		return createHttpClient(null, connectionManager);
	}

	public static AbstractHttpClient createHttpClient() {
		return createHttpClient(null, null);
	}

	public static AbstractHttpClient createHttpClient(HttpParams params) {
		return createHttpClient(params, null);
	}

	public static AbstractHttpClient createHttpClient(HttpParams params,
			ClientConnectionManager connectionManager) {
		if (params == null) {
			params = createHttpParams();
		}
		if (connectionManager == null) {
			connectionManager = createThreadSafeClientConnManager(params);
		}
		AbstractHttpClient httpClient = new DefaultHttpClient(
				connectionManager, params);
		addCustomHttpRequestInterceptor(httpClient);
		addCustomHttpResponseInterceptor(httpClient);
		setKeepAliveStrategy(httpClient);
		return httpClient;

	}

	public static HttpParams createHttpParams() {
		HttpParams params = new BasicHttpParams();
		HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
		HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
		HttpProtocolParams.setHttpElementCharset(params, "gbk");
		HttpProtocolParams.setUseExpectContinue(params, false);// 执行post方法时：true:发送请求头"Expect:100-continue"，false：则不送。

		HttpProtocolParams
				.setUserAgent(
						params,
						"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.23) Gecko/20110920 Firefox/3.6.23");

		HttpConnectionParams.setTcpNoDelay(params, true);
		HttpConnectionParams.setSocketBufferSize(params, socketBufferSize);
		HttpConnectionParams.setSoTimeout(params, soTimeout);
		HttpConnectionParams.setConnectionTimeout(params, connectionTimeout);

		// Increase max total connection to maxConnections
		ConnManagerParams.setMaxTotalConnections(params, maxConnections);
		// Increase default max connection per route to maxConnectionsPerRoute
		ConnPerRouteBean connPerRoute = new ConnPerRouteBean(
				maxConnectionsPerRoute);
		ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
		// ConnManagerParams.setTimeout(params, timeout);

		HttpClientParams.setCookiePolicy(params, CookiePolicy.BEST_MATCH);
		HttpClientParams.setRedirecting(params, false);
		return params;
	}

	public static ClientConnectionManager createThreadSafeClientConnManager(
			HttpParams params) {
		SchemeRegistry registry = new SchemeRegistry();
		registry.register(new Scheme("http", PlainSocketFactory
				.getSocketFactory(), 80));
		registry.register(new Scheme("https", SSLSocketFactory
				.getSocketFactory(), 443));
		if (params == null) {
			params = createHttpParams();
		}
		return new ThreadSafeClientConnManager(params, registry);
	}

	public static void shutdownConnectionManager() {
		idleConnectionEvictor.shutdown();
		try {
			idleConnectionEvictor.join(15000);
		} catch (InterruptedException e) {
		}

		// When ClientConnectionManager instance is no longer needed,
		// shut down the connection manager to ensure
		// immediate deallocation of all system resources
		connectionManager.shutdown();
	}

	public static IdleConnectionEvictor startIdleConnectionEvictor(
			ClientConnectionManager connectionManager) {
		IdleConnectionEvictor evictor = new IdleConnectionEvictor(
				connectionManager);
		evictor.start();
		return evictor;
	}

	public static IdleConnectionEvictor startIdleConnectionEvictor(
			ClientConnectionManager connectionManager, int idleTime) {
		IdleConnectionEvictor evictor = new IdleConnectionEvictor(
				connectionManager, idleTime);
		evictor.start();
		return evictor;
	}

	public static IdleConnectionEvictor startIdleConnectionEvictor(
			AbstractHttpClient httpClient, int idleTime) {

		return startIdleConnectionEvictor(httpClient.getConnectionManager(),
				idleTime);
	}

	public static IdleConnectionEvictor startIdleConnectionEvictor(
			AbstractHttpClient httpClient) {

		return startIdleConnectionEvictor(httpClient.getConnectionManager());
	}

	public static void setKeepAliveStrategy(AbstractHttpClient httpClient) {
		httpClient
				.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy() {
					@Override
					public long getKeepAliveDuration(HttpResponse response,
							HttpContext context) {
						long keepAlive = super.getKeepAliveDuration(response,
								context);
						if (keepAlive == -1) {
							// Keep connections alive n seconds if a keep-alive
							// value has not be explicitly set by the server
							keepAlive = keepAliveDuration;
						}
						return keepAlive;
					}
				});
	}

	public static HttpRequestInterceptor addCustomHttpRequestInterceptor(
			AbstractHttpClient httpClient) {
		httpClient.removeRequestInterceptorByClass(RequestUserAgent.class);

		HttpRequestInterceptor interceptor = new HttpRequestInterceptor() {
			public void process(HttpRequest request, HttpContext context)
					throws HttpException, IOException {
				if (!request.containsHeader(HTTP.USER_AGENT)) {
					request
							.setHeader(
									HTTP.USER_AGENT,
									"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.23) Gecko/20110920 Firefox/3.6.23");
				}
				if (!request.containsHeader("Accept")) {
					request.setHeader("Accept", "*/*");
				}
				if (!request.containsHeader("Accept-Encoding")) {
					// request.setHeader("Accept-Encoding", " gzip, deflate");
					request.setHeader("Accept-Encoding", " gzip");
				}

			}
		};
		httpClient.addRequestInterceptor(interceptor);
		return interceptor;
	}

	public static HttpResponseInterceptor addCustomHttpResponseInterceptor(
			AbstractHttpClient httpClient) {

		HttpResponseInterceptor interceptor = new HttpResponseInterceptor() {
			public void process(HttpResponse response, HttpContext context)
					throws HttpException, IOException {
				HttpEntity entity = response.getEntity();
				if (entity == null) {
					return;
				}
				Header ceheader = entity.getContentEncoding();
				if (ceheader != null) {
					HeaderElement[] codecs = ceheader.getElements();
					for (int i = 0; i < codecs.length; i++) {
						if (codecs[i].getName().equalsIgnoreCase("gzip")) {
							response.setEntity(new GzipDecompressingEntity(
									response.getEntity()));
							return;
						}
					}
				}
			}
		};
		httpClient.addResponseInterceptor(interceptor);
		return interceptor;
	}

	public static void setParams(HttpParams params) {
		HttpClientFactory.params = params;
	}

	public static HttpParams getParams() {
		return params;
	}

	public static void setConnectionManager(
			ClientConnectionManager connectionManager) {
		HttpClientFactory.connectionManager = connectionManager;
	}

	public static ClientConnectionManager getConnectionManager() {
		return connectionManager;
	}

	public static void setIdleConnectionEvictor(
			IdleConnectionEvictor idleConnectionEvictor) {
		HttpClientFactory.idleConnectionEvictor = idleConnectionEvictor;
	}

	public static IdleConnectionEvictor getIdleConnectionEvictor() {
		return idleConnectionEvictor;
	}

	public static void setConnectionIdleTime(int connectionIdleTime) {
		HttpClientFactory.connectionIdleTime = connectionIdleTime;
	}

	public static int getConnectionIdleTime() {
		return connectionIdleTime;
	}

	public static class GzipDecompressingEntity extends HttpEntityWrapper {

		public GzipDecompressingEntity(final HttpEntity entity) {
			super(entity);
		}

		@Override
		public InputStream getContent() throws IOException,
				IllegalStateException {

			// the wrapped entity's getContent() decides about repeatability
			InputStream wrappedin = wrappedEntity.getContent();

			return new GZIPInputStream(wrappedin);
		}

		@Override
		public long getContentLength() {
			// length of ungzipped content is not known
			return -1;
		}

	}

	public static class IdleConnectionEvictor extends Thread {

		private final ClientConnectionManager clientConnManager;

		private volatile boolean shutdown;

		private int idleTime = 120000;

		public IdleConnectionEvictor(ClientConnectionManager connectionManager) {
			super();
			this.clientConnManager = connectionManager;
		}

		public IdleConnectionEvictor(ClientConnectionManager connectionManager,
				int idleTime) {
			super();
			this.clientConnManager = connectionManager;
			this.idleTime = idleTime;
		}

		@Override
		public void run() {
			try {
				while (!shutdown) {
					synchronized (this) {
						wait(idleTime / 2);
						try {
							// Close expired connections
							clientConnManager.closeExpiredConnections();
							// Optionally, close connections
							// that have been idle longer than n sec
							clientConnManager.closeIdleConnections(idleTime,
									TimeUnit.MILLISECONDS);
						} catch (Exception e) {
							shutdown = true;
						}
					}
				}
			} catch (InterruptedException ex) {
				// terminate
			}
		}

		public void shutdown() {
			shutdown = true;
			synchronized (this) {
				notifyAll();
			}
		}

		public ClientConnectionManager getClientConnManager() {
			return clientConnManager;
		}

	}
}
